/****************************************************************************
**
** Copyright (C) 2009-2010 Mist Poryvaev.
** All rights reserved.
** Contact: Mist Poryvaev (mist.poryvaev@gmail.com)
**
** This file is part of the RailWorm project.
**
****************************************************************************/

#include "htmlgenie.h"

HtmlResponse::HtmlResponse(const QString &_data, const QUrl & _url) {
	data = _data;
	url = _url;
}

HtmlResponse::~HtmlResponse() {

}

QList<QWebElement> HtmlResponse::Generics(const QWebElement & _el, const HtmlElementPars & _pars, const QString & _tag) {
//	qDebug() << "generics tag = " << _el.tagName();
	QList<QWebElement> _list;
	bool _match;
	QWebElement _iter = _el.firstChild();
	while (_iter.isNull() == false) {
		_match = true;
		if (_tag.isEmpty() == false) {
			if (_iter.tagName().compare(_tag, Qt::CaseInsensitive) != 0) {
				_match = false;
			}
		}
		if (_pars.Name.isEmpty() == false) {
			if (_pars.Name.compare("*", Qt::CaseInsensitive) == 0) {
				if (_iter.attribute("name").isEmpty()) {
					_match = false;
				}
			}
			else if (_iter.attribute("name").compare(_pars.Name, Qt::CaseInsensitive) != 0) {
				_match = false;
			}

		}
		if (_pars.Class.isEmpty() == false) {
			if (_pars.Class.compare("*", Qt::CaseInsensitive) == 0) {
				if (_iter.attribute("class").isEmpty()) {
					_match = false;
				}
			}
			else if (_iter.attribute("class").compare(_pars.Class, Qt::CaseInsensitive) != 0) {
				_match = false;
			}
		}
		if (_pars.Id.isEmpty() == false) {
			if (_pars.Id.compare("*", Qt::CaseInsensitive) == 0) {
				if (_iter.attribute("id").isEmpty()) {
					_match = false;
				}
			}
			else if (_iter.attribute("id").compare(_pars.Id, Qt::CaseInsensitive) != 0) {
				_match = false;
			}
		}
		if (_pars.Href.isEmpty() == false) {
			if (_pars.Href.compare("*", Qt::CaseInsensitive) == 0) {
				if (_iter.attribute("href").isEmpty() && _iter.attribute("src").isEmpty()) {
					_match = false;
				}
			}
			else if ((_iter.attribute("href").compare(_pars.Href, Qt::CaseInsensitive) != 0) && (_iter.attribute("src").compare(_pars.Href, Qt::CaseInsensitive) != 0)) {
				_match = false;
			}
		}
		if (_pars.Content.isEmpty() == false) {
			if (_pars.Content.compare("*", Qt::CaseInsensitive) == 0) {
				if (_iter.toInnerXml().isEmpty()) {
					_match = false;
				}
			}
			else if (_iter.toInnerXml().contains(_pars.Content, Qt::CaseInsensitive) == false) {
				_match = false;
			}
		}
		if (_match) {
			_list << _iter;
		}
		_list << Generics(_iter, _pars, _tag);
		_iter = _iter.nextSibling();
	}
	return _list;
}

HtmlGenericDataList HtmlResponse::Generics(const QString & _data, const HtmlElementPars & _pars, const QString & _tag) {
	QWebPage _page;
	QWebSettings * _ws = _page.settings();
	_ws->setAttribute(QWebSettings::AutoLoadImages, false);
	_ws->setAttribute(QWebSettings::DnsPrefetchEnabled, false);
	_ws->setAttribute(QWebSettings::JavascriptEnabled, false);
	_ws->setAttribute(QWebSettings::JavaEnabled, false);
	_ws->setAttribute(QWebSettings::PluginsEnabled, false);
	_ws->setMaximumPagesInCache(0);
	QWebFrame * _frame = _page.mainFrame();
	_frame->setHtml(_data);
	HtmlGenericDataList _list;
	foreach(const QWebElement & _el, Generics(_frame->documentElement(), _pars, _tag)) {
		HtmlGenericData _generic;
		_generic.Content = _el.toInnerXml();
		_generic.Tag = _el.tagName();
		foreach(const QString & _at, _el.attributeNames()) {
			_generic.Attributes.append(_at, _el.attribute(_at));
		}
		_list << _generic;
	}
	return _list;
}

HtmlTableDataList HtmlResponse::Tables(const QString & _data, const HtmlElementPars & _pars) {
	HtmlTableDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "table");
	int _row = 0, _col = 0;
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlTableData _table;
		_table.Attributes = _generic.Attributes;
		_table.Content = _generic.Content;
		_table.Tag = _generic.Tag;
		bool _remove_thead = false;
		int _thead_idx = _table.Content.indexOf("thead");
		if (_thead_idx >= 0) {
			if (_table.Content.contains("table") == false) {
				_remove_thead = true;
			}
		}
		HtmlGenericDataList _tr_list = Generics("<table>" + _generic.Content + "</table>", HtmlElementPars(), "tr");
		_row = 0;
		foreach (const HtmlGenericData & _tr, _tr_list) {
			if ((_row == 0) && _remove_thead) {
				_row++;
				continue;
			}
			HtmlGenericDataList _td_list = Generics("<table><tr>" + _tr.Content + "</tr></table>", HtmlElementPars(), "td");
			_col = 0;
			foreach (const HtmlGenericData & _td, _td_list) {
				HtmlTableCellData _cell;
				_cell.Row = _row;
				_cell.Column = _col;
				_cell.Text = _td.Content;
				_cell.ColSpan = _td.Attributes.value("colspan").toString().toInt();
				if (_cell.ColSpan <= 0) {
					_cell.ColSpan = 1;
				}
				_table.Cells << _cell;
				_col += _cell.ColSpan;
			}
			_row++;
		}
		_table.Rows = _row;
		_table.Columns = _col;
		_list << _table;
	}
	return _list;
}

HtmlFormDataList HtmlResponse::Forms(const QString & _data, const HtmlElementPars & _pars) {
	HtmlFormDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "form");
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlFormData _form;
		_form.Attributes = _generic.Attributes;
		_form.Content = _generic.Content;
		_form.Tag = _generic.Tag;
		_form.Url = QUrl(_generic.Attributes.value("src").toString());
		_form.Method = _generic.Attributes.value("method").toString();
		_form.Action = _generic.Attributes.value("action").toString();
		HtmlElementPars _p;
		_p.Name = "*";
		HtmlGenericDataList _pars_list = Generics(_generic.Content, _p);
		foreach (const HtmlGenericData & _names, _pars_list) {
			QString _name = _names.Attributes.value("name").toString();
			if (_name.isEmpty() == false) {
				_form.Pars << _name;
			}
		}
		_list << _form;
	}
	return _list;
}

HtmlLinkDataList HtmlResponse::Links(const QString & _data, const HtmlElementPars & _pars) {
	HtmlLinkDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "a");
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlLinkData _link;
		_link.Attributes = _generic.Attributes;
		_link.Content = _generic.Content;
		_link.Tag = _generic.Tag;
		_link.Url = QUrl(_generic.Attributes.value("href").toString());
		_list << _link;
	}
	return _list;
}

HtmlLinkDataList HtmlResponse::Images(const QString & _data, const HtmlElementPars & _pars) {
	HtmlLinkDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "img");
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlLinkData _link;
		_link.Attributes = _generic.Attributes;
		_link.Content = "";
		_link.Tag = _generic.Tag;
		_link.Url = QUrl(_generic.Attributes.value("src").toString());
		_list << _link;
	}
	return _list;
}

HtmlSelectorDataList HtmlResponse::Selectors(const QString & _data, const HtmlElementPars & _pars) {
	HtmlSelectorDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "select");
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlSelectorData _selector;
		_selector.Attributes = _generic.Attributes;
		_selector.Content = _generic.Content;
		_selector.Tag = _generic.Tag;

		HtmlGenericDataList _options_list = Generics("<select>" + _selector.Content + "</select>", HtmlElementPars(), "option");
		foreach (const HtmlGenericData & _option, _options_list) {
			HtmlOptionData _option_data;
			_option_data.Text = _option.Content;
			_option_data.Value = _option.Attributes.value("value").toString();
			_selector.Options << _option_data;
		}
		_list << _selector;
	}
	return _list;
}

HtmlInputDataList HtmlResponse::Inputs(const QString & _data, const HtmlElementPars & _pars) {
	HtmlInputDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "input");
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlInputData _input;
		_input.Attributes = _generic.Attributes;
		_input.Content = _generic.Content;
		_input.Tag = _generic.Tag;
		_input.Text = _input.Content;
		_input.Value = _generic.Attributes.value("value").toString();
		_list << _input;
	}
	return _list;
}

HtmlScriptDataList HtmlResponse::Scripts(const QString & _data, const HtmlElementPars & _pars) {
	HtmlScriptDataList _list;
	HtmlGenericDataList _generic_list = Generics(_data, _pars, "script");
	foreach (const HtmlGenericData & _generic, _generic_list) {
		HtmlScriptData _script;
		_script.Attributes = _generic.Attributes;
		_script.Content = _generic.Content;
		_script.Tag = _generic.Tag;
		_script.Code = _script.Content;
		_script.Url = QUrl(_generic.Attributes.value("src").toString());
		_script.Language = _generic.Attributes.value("language").toString();
		_list << _script;
	}
	return _list;
}



HtmlGenericDataList HtmlResponse::Generics(const HtmlElementPars & _pars, const QString & _tag) {
	return Generics(data, _pars, _tag);
}

HtmlTableDataList HtmlResponse::Tables(const HtmlElementPars & _pars) {
	return Tables(data, _pars);
}

HtmlFormDataList HtmlResponse::Forms(const HtmlElementPars & _pars) {
	return Forms(data, _pars);
}

HtmlLinkDataList HtmlResponse::Links(const HtmlElementPars & _pars) {
	return Links(data, _pars);
}

HtmlLinkDataList HtmlResponse::Images(const HtmlElementPars & _pars) {
	return Images(data, _pars);
}

HtmlSelectorDataList HtmlResponse::Selectors(const HtmlElementPars & _pars) {
	return Selectors(data, _pars);
}

HtmlInputDataList HtmlResponse::Inputs(const HtmlElementPars & _pars) {
	return Inputs(data, _pars);
}

HtmlScriptDataList HtmlResponse::Scripts(const HtmlElementPars & _pars) {
	return Scripts(data, _pars);
}

const QString & HtmlResponse::Data() {
	return data;
}

const QUrl & HtmlResponse::Url() {
	return url;
}

// --------------------------------------------------------------

HtmlResponseHandler::HtmlResponseHandler(HtmlGenie * _jinn, int _id, const QUrl & _url, const QByteArray & _content_type_header): QObject() {
	id = _id;
	jinn = _jinn;
	reply = 0;
	jinn->handlers[id] = this;
//	page.setNetworkAccessManager(& jinn->NetMan());
//	connect(&page, SIGNAL(loadFinished(bool)), this, SLOT(PageFinished(bool)));
	request.setRawHeader("Host", QUrl(_url).host().toAscii());
	request.setRawHeader("User-Agent", "RailWorm");
	request.setHeader(QNetworkRequest::ContentTypeHeader, _content_type_header);
	request.setHeader(QNetworkRequest::ContentLengthHeader, 0);
	request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
	request.setUrl(_url);
}

HtmlResponseHandler::~HtmlResponseHandler() {
	if (reply) {
		reply->deleteLater();
	}
}

void HtmlResponseHandler::Request() {
//	page.mainFrame()->load(request, QNetworkAccessManager::GetOperation, QByteArray());
	if (reply) {
		reply->deleteLater();
	}
	reply = jinn->NetMan().get(request);
	connect(reply, SIGNAL(finished()), this, SLOT(PageFinished()));
	QTimer::singleShot(60000, this, SLOT(Timeout()));
}

void HtmlResponseHandler::PageFinished() {
//	qDebug() << "finished id = " << id << ", url = " << page.mainFrame()->url().toString();
//	emit Response(new HtmlResponse(page.mainFrame()->toHtml()), id, !_ok);
	QUrl _redirect_url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
	if (_redirect_url.isEmpty() == false) {
		request.setRawHeader("Host", QUrl(_redirect_url).host().toAscii());
		request.setUrl(_redirect_url);
		Request();
		return;
	}
	bool _ok = (reply->error() == QNetworkReply::NoError);
	QByteArray _html = reply->readAll();

	QWebPage page;
	page.mainFrame()->setHtml(_html);
	QString _charset = "utf-8";
	QString _meta_content;
	foreach (const QString & _key, page.mainFrame()->metaData().keys()) {
		if (_key.compare("content", Qt::CaseInsensitive) == 0) {
			_meta_content =  page.mainFrame()->metaData().value(_key);
			break;
		}
	}
	foreach (const QString & _content, _meta_content.split(";")) {
		qDebug() << _content;
		if (_content.simplified().contains("charset")) {
			_charset = _content.simplified().split("=").at(1);
			break;
		}
	}


	QString _unicode_html = QTextCodec::codecForName(_charset.toAscii())->toUnicode(_html);
	qDebug() << "finished id = " << id << ", charset = " << _charset;
	emit Response(new HtmlResponse(_unicode_html, reply->url()), id, !_ok);
	jinn->handlers.remove(id);
	deleteLater();
}

void HtmlResponseHandler::Cancel() {
//	page.action(QWebPage::Stop)->trigger();
	if (reply) {
		reply->abort();
	}
}

void HtmlResponseHandler::Timeout() {
	qDebug() << "timeout, id = " << id;
	Cancel();
}

// --------------------------------------------------------------

int HtmlGenie::id = 0;
QNetworkAccessManager HtmlGenie::net_man;

HtmlGenie::HtmlGenie(QObject * _parent) :
	QObject(_parent) {

	connect(& net_man, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(OnSslErrors(QNetworkReply*,QList<QSslError>)));
}

HtmlGenie::~HtmlGenie() {}

QNetworkAccessManager & HtmlGenie::NetMan() {
	return net_man;
}

int HtmlGenie::Request(const QUrl & _url, const QByteArray & _content_type_header, QObject * Receiver, const char * Slot) {
	qDebug() << "HtmlGenie::Request " << _url.toString();
	int _id = id;
	HtmlResponseHandler * _handler = new HtmlResponseHandler(this, _id, _url, _content_type_header.size() > 0 ? _content_type_header : ContentTypeForm());
	connect(
			_handler,
			SIGNAL(Response(HtmlResponse *, int, bool)),
			Receiver,
			Slot
			);
	_handler->Request();
	id++;
	return _id;
}

void HtmlGenie::Cancel() {
	foreach (HtmlResponseHandler * _handler, handlers.values()) {
		_handler->Cancel();
	}
}

void HtmlGenie::OnSslErrors(QNetworkReply * reply, const QList<QSslError> & errors) {
	reply->ignoreSslErrors();
}

